/*
安装此模块，将
    在/proc/devices文件里生成设备(lxz_dev)及其主设备号
    在/sys/class/生成lxz_class目录
    在/sys/class/lxz_class/下生成lxz_device设备节点(文件夹)
    在/dev/下生成lxz_device设备节点，可read\write\ioctl

    read在未设置fctl标志的非阻塞IO情况下，默认为阻塞调用
    本例
    1.使用等待队列实现非阻塞IO，一个用户态进程读取数据，一个用户态进程写入数据，直到写进程成功写入读进程才能返回
    2.支持非阻塞IO
*/

#include "linux/init.h"
#include "linux/module.h"
#include "linux/device.h"
#include "linux/fs.h"
#include "linux/string.h"
#include "linux/uaccess.h"
#include "linux/wait.h"
#include "linux/atomic.h"

#define MODULE_BUFFER_SIZE 1024

static dev_t chrdev_region_id = 0;
static const char *chrdev_region_name = "lxz_chrdev_region";
static const char *sysfs_class_name = "lxz_class";
static struct class *sysfs_class = NULL;
static struct device *sysfs_device = NULL;
static const char *sysfs_device_name = "lxz_device";
static char module_buffer[MODULE_BUFFER_SIZE] = {0}; // 本模块的缓存
static atomic_t buffer_count = ATOMIC_INIT(0);       // 本模块的缓存已经存储的字节数
static DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);     // 定义初始化等待队列(头)，用于读
static DECLARE_WAIT_QUEUE_HEAD(write_wait_queue);    // 定义初始化等待队列(头)，用于写

int chrdev_open(struct inode *node, struct file *f)
{
    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

int chrdev_release(struct inode *node, struct file *f)
{
    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

// 阻塞方式读
ssize_t chrdev_read_block(struct file *f, char __user *buffer, size_t n, loff_t *pos)
{
    size_t buffered_bytes = 0; // buffer内部缓存的字节数
    size_t to_copy = 0;        // 实际操作，读取的字节数
    unsigned long remain = 0;

    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    // 等待buffer_count就绪，系统调用将进入读等待
    wait_event_interruptible(read_wait_queue, (atomic_read(&buffer_count) > 0));

    // 读取存储的字节数
    buffered_bytes = atomic_read(&buffer_count);
    printk("wait_event_interruptible passed,buffered_bytes=%lu\n", buffered_bytes);

    to_copy = n < buffered_bytes ? n : buffered_bytes;
    printk("n=%lu,to_copy=%lu\n", n, to_copy);

    // 拷贝到用户空间
    remain = copy_to_user(buffer, module_buffer, to_copy);
    if (remain)
    {
        printk("remain(%lu),to_copy(%lu),what a bug\n", remain, to_copy);
        return -EFAULT;
    }

    // 若有未拷贝完的数据，移动buffer
    if (buffered_bytes - to_copy)
    {
        memmove(module_buffer, module_buffer + to_copy, buffered_bytes - to_copy);
    }

    // 设置原子变量的值
    atomic_set(&buffer_count, buffered_bytes - to_copy);

    // 唤醒写等待队列中的进程
    wake_up(&write_wait_queue);

    return to_copy;
}

ssize_t chrdev_read_nonblock(struct file *f, char __user *buffer, size_t n, loff_t *pos)
{
    size_t buffered_bytes = 0; // buffer内部缓存的字节数
    size_t to_copy = 0;        // 实际操作，读取的字节数
    unsigned long remain = 0;

    // 判定buffer是否有数据
    buffered_bytes = atomic_read(&buffer_count);

    // 若buffer内无数据，则返回-EAGAIN
    if (buffered_bytes == 0)
    {
        printk("buffered_bytes==0\n");
        return -EAGAIN;
    }

    to_copy = n < buffered_bytes ? n : buffered_bytes;
    printk("n=%lu,to_copy=%lu\n", n, to_copy);

    // 拷贝到用户空间
    remain = copy_to_user(buffer, module_buffer, to_copy);
    if (remain)
    {
        printk("remain(%lu),to_copy(%lu),what a bug\n", remain, to_copy);
        return -EFAULT;
    }

    // 若有未拷贝完的数据
    if (buffered_bytes - to_copy)
    {
        memmove(module_buffer, module_buffer + to_copy, buffered_bytes - to_copy);
    }

    // 设置缓存的字节数
    atomic_set(&buffer_count, (buffered_bytes - to_copy));

    return to_copy;
}

ssize_t chrdev_read(struct file *f, char __user *buffer, size_t n, loff_t *pos)
{
    // 非阻塞方式读
    if (f->f_flags & O_NONBLOCK)
    {
        return chrdev_read_nonblock(f, buffer, n, pos);
    }

    // 阻塞方式读
    return chrdev_read_block(f, buffer, n, pos);
}

// 阻塞方式写
ssize_t chrdev_write_block(struct file *f, char __user *buffer, size_t n, loff_t *pos)
{
    size_t max_size = MODULE_BUFFER_SIZE;         // 缓存可写入的字节数
    size_t to_copy = n < max_size ? n : max_size; // 实际操作，写入的字节数
    unsigned long remain = 0;

    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    printk("n=%lu,to_copy=%lu\n", n, to_copy);

    // 等待buffer_count==0就绪，系统调用将进入写等待
    wait_event_interruptible(write_wait_queue, (atomic_read(&buffer_count) == 0));

    // 从用户态拷贝数据
    remain = copy_from_user(module_buffer, buffer, to_copy);
    if (remain)
    {
        printk("remain(%lu),to_copy(%lu),what a bug\n", remain, to_copy);
        return -EFAULT;
    }
    printk("module_buffer=%s\n", module_buffer);

    // 设置原子变量的值
    atomic_set(&buffer_count, to_copy);

    // 唤醒读等待队列中的进程
    wake_up(&read_wait_queue);

    return to_copy;
}

ssize_t chrdev_write_nonblock(struct file *f, char __user *buffer, size_t n, loff_t *pos)
{
    int buffered_bytes = atomic_read(&buffer_count);          // 读取已经缓存的字节数
    size_t to_copy = n < buffered_bytes ? n : buffered_bytes; // 计算写操作实际要写的字节数
    unsigned long remain = 0;

    // 若已经有缓存的字节数，直接返回，下次再写，直到读取进程读完
    if (buffered_bytes != 0)
    {
        printk("buffered_bytes(%d) !=0,wait to read away\n", buffered_bytes);
        return -EAGAIN;
    }

    remain = copy_from_user(module_buffer, buffer, to_copy);
    if (remain)
    {
        printk("copy_from_user error\n");
        return -EFAULT;
    }

    // 设置缓存的字节数
    atomic_set(&buffer_count, to_copy);

    return to_copy;
}

ssize_t chrdev_write(struct file *f, const char __user *buffer, size_t n, loff_t *pos)
{
    // 非阻塞方式写
    if (f->f_flags & O_NONBLOCK)
    {
        return chrdev_write_nonblock(f, buffer, n, pos);
    }

    // 阻塞方式写
    return chrdev_write_block(f, buffer, n, pos);
}

#define LXZ_MAGIC 0xff
long chrdev_ioctl(struct file *f, unsigned int ioctl_cmd, unsigned long arg)
{
    static const unsigned int GETNUM = _IOR(LXZ_MAGIC, 0, unsigned int);
    static const unsigned int PUTNUM = _IOW(LXZ_MAGIC, 1, unsigned int);

    static unsigned int num = 11; // 本模块缓存的静态变量
    int ret = -1;
    unsigned int __user *ptr = (unsigned int *)arg; // 用户态地址

    if (ioctl_cmd == GETNUM)
    {
        printk("GETNUM\n");
        ret = put_user(num, ptr);
        if (ret != 0)
        {
            printk("put_user error\n");
            return -1;
        }
    }
    else if (ioctl_cmd == PUTNUM)
    {
        printk("PUTNUM\n");
        ret = get_user(num, ptr);
        if (ret != 0)
        {
            printk("get_user error\n");
            return -1;
        }
        printk("get user succeed,num=%u\n", num);
    }
    else
    {
        return -1;
    }

    return 0;
}

static struct file_operations chrdev_fops = {
    .read = chrdev_read,
    .write = chrdev_write,
    .open = chrdev_open,
    .release = chrdev_release,
    .unlocked_ioctl = chrdev_ioctl,
};

static int __init chrdev_init(void)
{
    int ret = -1;

    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    /*
    注册字符设备
    若major=0，则自动注册，成功并返回主设备号，失败返回负数
    若major>0，则尝试基于该主设备号进行注册，失败返回负数
    */
    ret = register_chrdev(0, chrdev_region_name, &chrdev_fops);
    if (ret < 0)
    {
        printk("register_chrdev error\n");
        return -1;
    }
    printk("register_chrdev succeed,ret=%d\n", ret);
    chrdev_region_id = MKDEV(ret, 0);

    // 创建sysfs设备类别
    sysfs_class = class_create(THIS_MODULE, sysfs_class_name);
    if (IS_ERR(sysfs_class))
    {
        printk("class_create error\n");

        unregister_chrdev(MAJOR(chrdev_region_id), chrdev_region_name);
        return -1;
    }
    printk("class_create succeed\n");

    // 创建基于设备号和sysfs设备类别的设备，将导出到/sys/class/
    sysfs_device = device_create(sysfs_class, NULL, chrdev_region_id, NULL, sysfs_device_name);
    if (IS_ERR(sysfs_device))
    {
        printk("device_create error\n");

        class_destroy(sysfs_class);
        unregister_chrdev(MAJOR(chrdev_region_id), chrdev_region_name);
        return -1;
    }
    printk("device_create succeed\n");

    return 0;
}

static void __exit chrdev_exit(void)
{
    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    device_destroy(sysfs_class, chrdev_region_id);

    class_destroy(sysfs_class);

    unregister_chrdev(MAJOR(chrdev_region_id), chrdev_region_name);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lxz");
